﻿///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2011, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using MTFOrg.MTF.MTFRuntime;
using MTFOrg.MTF.MTFCore.Messages;

namespace MTFOrg.MTF.MTFCore
{
    public class ProducerConsumerElement
    {
        /// <summary>
        /// User data tagged to this element.
        /// </summary>
        protected object m_Tag;

        /// <summary>
        /// Array of allocated slots.
        /// </summary>
        protected Slots m_Slots;

        /// <summary>
        /// Number of consumer links
        /// </summary>
        protected int m_NumConsumerLinks;

        /// <summary>
        /// Number of producer links
        /// </summary>
        protected int m_NumProducerLinks;


        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="numConsumerSlots">Number of consumer slots per link</param>
        /// <param name="numProducerSlots">Number of producer slots per link</param>
        /// <param name="numConsumerLinks">Number of consumer links</param>
        /// <param name="numProducerLinks">Number of producer links</param>
        public ProducerConsumerElement(int numConsumerSlots, int numProducerSlots, int numConsumerLinks, int numProducerLinks, SlotAdvanceMode producerAdvanceMode)
        {
            m_NumConsumerLinks = numConsumerLinks;
            m_NumProducerLinks = numProducerLinks;
            m_Slots = new Slots(m_NumConsumerLinks, m_NumProducerLinks, numConsumerSlots, numProducerLinks, producerAdvanceMode == SlotAdvanceMode.AdvanceAll);
        }

        /// <summary>
        /// Gets or sets user data associated with this element.
        /// </summary>
        public object Tag
        {
            get { return m_Tag; }
            set { m_Tag = value; }
        }


        // ********************* CONSUMER INTERFACE ***********************

        /// <summary>
        /// Gets the slot set for the specified un-opened consumer link.
        /// </summary>
        /// <param name="consumerLinkNum">Consumer link number</param>
        /// <returns>Slot set holding current query for specified link</returns>
        public int GetConsumerSlotsFromLinkNum(int consumerLinkNum)
        {
            return m_Slots.GetConsumerSlotsInUse(consumerLinkNum);
        }

        /// <summary>
        /// Gets the consumer slot associated with the given producer
        /// slot, if there is one.
        /// </summary>
        /// <param name="producerSlot">Producer slot</param>
        /// <returns>Consumer slot associated with given producer slot, or null</returns>
        public int GetConsumerSlotsFromProducerSlots(int producerSlot)
        {
            return m_Slots.GetProducerOppositeSlot(producerSlot);
        }

        /// <summary>
        /// Clears the specified link.
        /// </summary>
        /// <param name="linkNum">Number of link to clear</param>
        public void ClearLink(int linkNum)
        {
            m_Slots.IncConsumerNumCompletedLinks(m_Slots.GetConsumerSlotsInUse(linkNum));
            m_Slots.SetConsumerSlotsInUse(linkNum, Slots.NO_SLOT);
        }

        /// <summary>
        /// Attempts to send a query and returns whether it was successful.
        /// </summary>
        /// <param name="linkNum">Link number to test</param>
        /// <param name="producerSlot">Producer slot</param>
        /// <param name="consumerSlot">Consumer slot</param>
        /// <returns>true if request is allowed, otherwise false</returns>
        public int QuerySent(int linkNum, int producerSlot, SlotAdvanceMode slotAdvanceMode)
        {
            // Check for an existing slot to use
            int consumerSlot = m_Slots.GetConsumerSlotsInUse(linkNum);
            if (consumerSlot != Slots.NO_SLOT)
            {
#if DEBUG
                Debug.Assert(m_Slots.GetConsumerLinkStatus(consumerSlot, linkNum) == LinkStatus.Inactive, "Attempt to query a link that is not inactive.");
#endif
                // Consumer slot is ready to be queried on this link
                m_Slots.SetConsumerLinkStatus(consumerSlot, linkNum, LinkStatus.Waiting);

                // If the producer slot exists then link it to this consumer slot
                if (producerSlot != Slots.NO_SLOT)
                {
                    m_Slots.SetProducerOppositeSlot(producerSlot, consumerSlot);
                    m_Slots.SetConsumerOppositeSlot(consumerSlot, producerSlot);
                }

                return consumerSlot;
            }

            // Try to get a new slot to send in
            int nextSlot = m_Slots.GetNextFreeConsumerSlot();
            if (nextSlot != Slots.NO_SLOT)
            {
                // Set the link status to waiting
                m_Slots.SetConsumerLinkStatus(nextSlot, linkNum, LinkStatus.Waiting);

                // If the producer slot exists then link it to this consumer slot
                if (producerSlot != Slots.NO_SLOT)
                {
                    m_Slots.SetProducerOppositeSlot(producerSlot, nextSlot);
                    m_Slots.SetConsumerOppositeSlot(nextSlot, producerSlot);
                }

                // Assign links to use the new slot
                for (int link = 0; link < m_NumConsumerLinks; link++)
                {
                    int slot = m_Slots.GetConsumerSlotsInUse(link);

                    if (slot == Slots.NO_SLOT)
                    {
                        m_Slots.SetConsumerSlotsInUse(link, nextSlot);
                    }
                    else if (m_Slots.GetConsumerNextSlot(slot) == Slots.NO_SLOT)
                    {
                        m_Slots.SetConsumerNextSlot(slot, nextSlot);
                    }
                }

                // Return new slot
                return nextSlot;
            }
            else
            {
                // Return false if slot not available
                return Slots.NO_SLOT;
            }
        }

        /// <summary>
        /// Notifies of an event received on a particular consumer link.
        /// </summary>
        /// <param name="linkNum">Link number event was received on</param>
        /// <param name="slotAdvanceMode">Mode for advancing the active slot</param>
        public void EventReceived(int linkNum, SlotAdvanceMode slotAdvanceMode)
        {
            int consumerSlot = m_Slots.GetConsumerSlotsInUse(linkNum);

#if DEBUG
            Debug.Assert(consumerSlot != Slots.NO_SLOT, "Event was received on a consumer link with no active slot.");
            Debug.Assert(m_Slots.GetConsumerLinkStatus(consumerSlot, linkNum) == LinkStatus.Waiting, "Event was received on a consumer link that was not waiting.");
#endif

            // Set link to opened
            m_Slots.SetConsumerLinkStatus(consumerSlot, linkNum, LinkStatus.Opened);

            // Advance the active slot in-use
            if (slotAdvanceMode == SlotAdvanceMode.AdvanceSingle)
            {
                // Check whether the next slot has already been created
                int nextSlot = m_Slots.GetConsumerNextSlot(consumerSlot);
                if (nextSlot != Slots.NO_SLOT)
                {
                    // Set as the active slot for this link
                    m_Slots.SetConsumerSlotsInUse(linkNum, nextSlot);
                    return;
                }

                // Create new slot
                nextSlot = m_Slots.GetNextFreeConsumerSlot();

                // Set as the active slot for this link
                m_Slots.SetConsumerSlotsInUse(linkNum, nextSlot);

                // Link the previous slot to this new slot
                m_Slots.SetConsumerNextSlot(consumerSlot, nextSlot);

                // Assign unassigned links to use the new slot
                for (int link = 0; link < m_NumConsumerLinks; link++)
                    if (m_Slots.GetConsumerSlotsInUse(link) == Slots.NO_SLOT)
                        m_Slots.SetConsumerSlotsInUse(link, nextSlot);
            }
            else // if (slotAdvanceMode == SlotAdvanceMode.AdvanceAll)
            {
                // Get next free slot
                int nextSlot = m_Slots.GetNextFreeConsumerSlot();

                // Move all waiting links to the next slot if there is one
                if (nextSlot != Slots.NO_SLOT)
                {
                    for (int link = 0; link < m_NumConsumerLinks; link++)
                    {
                        if (m_Slots.GetConsumerLinkStatus(consumerSlot, link) == LinkStatus.Waiting)
                        {
                            m_Slots.SetConsumerLinkStatus(nextSlot, link, LinkStatus.Waiting);
                            m_Slots.SetConsumerLinkStatus(consumerSlot, link, LinkStatus.Inactive);
                        }
                        m_Slots.SetConsumerSlotsInUse(link, nextSlot);
                    }
                }
                else
                {
                    // Check if there are any waiting links that can't be advanced
                    for (int link = 0; link < m_NumConsumerLinks; link++)
                    {
#if DEBUG
                        Debug.Assert(m_Slots.GetConsumerLinkStatus(consumerSlot, link) != LinkStatus.Waiting,
                            "Attempt to advance query failed as no slot was available.");
#endif
                        m_Slots.SetConsumerSlotsInUse(link, Slots.NO_SLOT);
                    }
                }
            }
        }

        /// <summary>
        /// Notifies of an event release on a consumer link.
        /// </summary>
        /// <param name="consumerSlot">Consumer slot</param>
        /// <param name="consumerLinkNum">Link number release was received on</param>
        /// <param name="slotAdvanceMode">Mode for advancing the active slot</param>
        public void EventReleased(int consumerSlot, int consumerLinkNum, SlotAdvanceMode slotAdvanceMode)
        {
            if (slotAdvanceMode == SlotAdvanceMode.AdvanceSingle)
            {
                // Return used slot when all links complete
                if (m_Slots.IncConsumerNumCompletedLinks(consumerSlot) == m_NumConsumerLinks)
                    m_Slots.ReturnUsedConsumerSlot(consumerSlot);
            }
            else
            {
                // Return used slot
                m_Slots.ReturnUsedConsumerSlot(consumerSlot);
            }
        }


        // ********************* PRODUCER INTERFACE ***********************

        /// <summary>
        /// Gets the producer slot associated with a given consumer
        /// slot.
        /// </summary>
        /// <param name="consumerSlot">Consumer slot</param>
        /// <returns>Producer slot set associated with consumer slot set</returns>
        public int GetProducerSlotsFromConsumerSlots(int consumerSlot)
        {
            return m_Slots.GetConsumerOppositeSlot(consumerSlot);
        }

        /// <summary>
        /// Gets the producer slot active on the given producer link.
        /// </summary>
        /// <param name="producerLinkNum">Producer link number</param>
        /// <returns>Producer slot set active on given producer link</returns>
        public int GetProducerSlotsFromLinkNum(int producerLinkNum)
        {
            return m_Slots.GetProducerSlotsInUse(producerLinkNum);
        }

        /// <summary>
        /// Notifies of a query received on a producer link.
        /// </summary>
        /// <param name="linkNum">Link number query was received on</param>
        /// <param name="slotAdvanceMode">Mode for advancing to the next slot</param>
        /// <returns>Producer slot</returns>
        public int QueryReceived(int linkNum, SlotAdvanceMode slotAdvanceMode)
        {
            int producerSlot;

            if (slotAdvanceMode == SlotAdvanceMode.AdvanceSingle)
            {
                // Find slot set with space to query
                producerSlot = m_Slots.GetProducerSlotsInUse(linkNum);
                if (producerSlot != Slots.NO_SLOT)
                {
                    // Found slot set with space to query (use this).
                    m_Slots.SetProducerLinkStatus(producerSlot, linkNum, LinkStatus.Waiting);
                }
                else
                {
                    // Create new slot set
                    producerSlot = m_Slots.GetNextFreeProducerSlot();
                    m_Slots.SetProducerLinkStatus(producerSlot, linkNum, LinkStatus.Waiting);

                    // If any link doesn't have a producer assigned then assign the new slot set
                    for (int link = 0; link < m_NumProducerLinks; link++)
                    {
                        int slot = m_Slots.GetProducerSlotsInUse(link);
                        if (slot == Slots.NO_SLOT)
                            m_Slots.SetProducerSlotsInUse(link, producerSlot);
                        else
                            m_Slots.SetProducerNextSlot(slot, producerSlot);
                    }
                }
            }
            else // if (slotAdvanceMode == SlotAdvanceMode.AdvanceAll)
            {
                // Get a free producer slot
                producerSlot = m_Slots.GetNextFreeProducerSlot();
                m_Slots.SetProducerLinkStatus(producerSlot, linkNum, LinkStatus.Waiting);

                //for (int link = 0; link < m_NumProducerLinks; link++)
                m_Slots.SetProducerSlotsInUse(linkNum, producerSlot);
            }

            return producerSlot;
        }

        /// <summary>
        /// Notifies of an event sent on a producer link.
        /// </summary>
        /// <param name="linkNum">Link number request was received on</param>
        /// <param name="producerSlot">Producer slot</param>
        public void EventSent(int linkNum, int producerSlot, SlotAdvanceMode slotAdvanceMode)
        {
#if DEBUG
            Debug.Assert(producerSlot != Slots.NO_SLOT, "Attempt to send event on unassigned producer link.");
            Debug.Assert(m_Slots.GetProducerLinkStatus(producerSlot, linkNum) == LinkStatus.Waiting, "Attempt to send on a producer link that was not waiting.");
#endif

            // Set link status to opened
            m_Slots.SetProducerLinkStatus(producerSlot, linkNum, LinkStatus.Opened);

            if (slotAdvanceMode == SlotAdvanceMode.AdvanceSingle)
            {
                // Find next producer slot in sequence
                int nextSlot = m_Slots.GetProducerNextSlot(producerSlot);
                if (nextSlot != Slots.NO_SLOT)
                {
                    // Set as the active slot for this link
                    m_Slots.SetProducerSlotsInUse(linkNum, nextSlot);
                }
                else
                {
                    // Create new slot
                    nextSlot = m_Slots.GetNextFreeProducerSlot();
                    
                    // Set as the active slot for this link
                    m_Slots.SetProducerSlotsInUse(linkNum, nextSlot);

                    // Link the previous slot to this new slot
                    m_Slots.SetProducerNextSlot(producerSlot, nextSlot);
                }
            }
            else // if (slotAdvanceMode == SlotAdvanceMode.AdvanceAll)
            {
                // Get a free producer slot
                m_Slots.SetProducerSlotsInUse(linkNum, Slots.NO_SLOT);
            }
        }

        /// <summary>
        /// Notifies of an event released on a producer link.
        /// </summary>
        /// <param name="linkNum">Link number request was received on</param>
        /// <param name="producerSlot">Producer slot</param>
        /// <param name="slotAdvanceMode">Slot advance mode</param>
        /// <returns>true if the slot is marked as deletable</returns>
        public bool PreReleaseReceived(int linkNum, int producerSlot, SlotAdvanceMode slotAdvanceMode)
        {
#if DEBUG
            if (m_Slots.GetProducerLinkStatus(producerSlot, linkNum) != LinkStatus.Opened)
                throw new Exception("Attempt to release a producer link that was not open.");
#endif

            // Set link status to released
            m_Slots.SetProducerLinkStatus(producerSlot, linkNum, LinkStatus.Released);

            // Determine if the slot can be disposed of
            bool isDeletable;
            if (slotAdvanceMode == SlotAdvanceMode.AdvanceAll)
            {
                isDeletable = true;
            }
            else
            {
                bool foundActive = false;
                for (int link = 0; link < m_NumProducerLinks; link++)
                {
                    if (m_Slots.GetProducerLinkStatus(producerSlot, link) != LinkStatus.Released)
                    {
                        foundActive = true;
                        break;
                    }
                }
                isDeletable = !foundActive;
            }

            return isDeletable;
        }

        /// <summary>
        /// Notifies of an event released on a producer link.
        /// </summary>
        /// <param name="linkNum">Link number request was received on</param>
        /// <param name="producerSlot">Producer slot</param>
        /// <param name="slotAdvanceMode">Slot advance mode</param>
        /// <param name="canDelete">true if the slot can be deleted</param>
        public bool PostReleaseReceived(int linkNum, int producerSlot, SlotAdvanceMode slotAdvanceMode, bool canDelete)
        {
            // Delete the slot if completed
            if (canDelete)
            {
                m_Slots.ReturnUsedProducerSlot(producerSlot);
            }

            return canDelete;
        }

        /// <summary>
        /// Gets the slots store structure.
        /// </summary>
        /// <returns>Slots store</returns>
        public Slots GetSlots()
        {
            return m_Slots;
        }
    }
}